package com.ifonly.common.typeconverter.impl;

import com.ifonly.common.typeconverter.Converter;
import com.ifonly.common.typeconverter.TypeConversionException;
import com.ifonly.common.typeconverter.TypeConvert;

import java.lang.reflect.Constructor;
import java.util.*;

/**
 * @author <a href="mailto:ifonlymaster@163.com">ifonly</a>
 * @version 1.0 2015-12-18 10:42
 * @since JDK 1.6
 */
public class CollectionConverter<T> implements Converter<Collection<T>> {

    protected final Class<? extends Collection> collectionType;
    protected final Class<T> targetComponentType;

    public CollectionConverter( Class<? extends Collection> collectionType,
            Class<T> targetComponentType) {
        this.collectionType = collectionType;
        this.targetComponentType = targetComponentType;
    }

    @Override
    public Collection<T> convert(Object value) {
        if (value == null) {
            return null;
        }

        if (!(value instanceof Collection)) {
            return convertValueToCollection(value);
        }

        return convertCollectionToCollection((Collection) value);
    }

    protected T convertType(Object value) {
        return TypeConvert.convert(targetComponentType, value);
    }

    protected Collection<T> convertValueToCollection(Object value) {
        if (value instanceof Iterable) {
            Iterable iterable = (Iterable) value;
            Collection<T> collection = createCollection(0);

            for (Object element : iterable) {
                collection.add(convertType(element));
            }
            return collection;
        }

        Class type = value.getClass();

        if (type.isArray()) {
            // convert arrays
            Class componentType = type.getComponentType();

            if (componentType.isPrimitive()) {
                return convertPrimitiveArrayToCollection(value, componentType);
            } else {
                Object[] array = (Object[]) value;
                Collection<T> result = createCollection(array.length);
                for (Object a : array) {
                    result.add(convertType(a));
                }
                return result;
            }
        }

        // everything else:
        return convertToSingleElementCollection(value);
    }

    @SuppressWarnings("unchecked")
    protected Collection<T> convertToSingleElementCollection(Object value) {
        Collection<T> collection = createCollection(0);

        collection.add((T) value);

        return collection;
    }

    @SuppressWarnings("AutoBoxing")
    protected Collection<T> convertPrimitiveArrayToCollection(Object value, Class primitiveComponentType) {
        Collection<T> result = null;

        if (primitiveComponentType == int.class) {
            int[] array = (int[]) value;
            result = createCollection(array.length);
            for (int a : array) {
                result.add(convertType(a));
            }
        } else if (primitiveComponentType == long.class) {
            long[] array = (long[]) value;
            result = createCollection(array.length);
            for (long a : array) {
                result.add(convertType(a));
            }
        } else if (primitiveComponentType == float.class) {
            float[] array = (float[]) value;
            result = createCollection(array.length);
            for (float a : array) {
                result.add(convertType(a));
            }
        } else if (primitiveComponentType == double.class) {
            double[] array = (double[]) value;
            result = createCollection(array.length);
            for (double a : array) {
                result.add(convertType(a));
            }
        } else if (primitiveComponentType == short.class) {
            short[] array = (short[]) value;
            result = createCollection(array.length);
            for (short a : array) {
                result.add(convertType(a));
            }
        } else if (primitiveComponentType == byte.class) {
            byte[] array = (byte[]) value;
            result = createCollection(array.length);
            for (byte a : array) {
                result.add(convertType(a));
            }
        } else if (primitiveComponentType == char.class) {
            char[] array = (char[]) value;
            result = createCollection(array.length);
            for (char a : array) {
                result.add(convertType(a));
            }
        } else if (primitiveComponentType == boolean.class) {
            boolean[] array = (boolean[]) value;
            result = createCollection(array.length);
            for (boolean a : array) {
                result.add(convertType(a));
            }
        }
        return result;
    }

    protected Collection<T> convertCollectionToCollection(Collection value) {
        Collection<T> collection = createCollection(value.size());

        for (Object v : value) {
            collection.add(convertType(v));
        }

        return collection;
    }

    @SuppressWarnings("unchecked")
    protected Collection<T> createCollection(int length) {
        if (collectionType.isInterface()) {
            if (collectionType == List.class) {
                if (length > 0) {
                    return new ArrayList<T>(length);
                } else {
                    return new ArrayList<T>();
                }
            }

            if (collectionType == Set.class) {
                if (length > 0) {
                    return new HashSet<T>(length);
                } else {
                    return new HashSet<T>();
                }
            }

            throw new TypeConversionException("Unknown collection: " + collectionType.getName());
        }
        if (length > 0) {
            try {
                Constructor<Collection<T>> ctor = (Constructor<Collection<T>>) collectionType.getConstructor(int.class);
                return ctor.newInstance(length);
            } catch (Exception ex) {
                // ignore exception
            }
        }

        try {
            return collectionType.newInstance();
        } catch (Exception ex) {
            throw new TypeConversionException(ex);
        }
    }

}
